"""MySensors constants for version 1.4 of MySensors."""
from enum import IntEnum

import voluptuous as vol

from mysensors.validation import is_version, percent_int
from .handler import HANDLERS


def get_handler_registry():
    """Return handler registry for this version."""
    return HANDLERS


class BaseConst(IntEnum):
    """MySensors message types."""

    def get_handler(self, handler_registry):
        """Return correct message handler."""
        return handler_registry.get(self.name)

    def set_handler(self, handler_registry, function):
        """Set message handler for name."""
        handler_registry[self.name] = function


class MessageType(BaseConst):
    """MySensors message types."""

    # pylint: disable=invalid-name
    presentation = 0  # sent by a node when presenting attached sensors
    set = 1  # sent from/to sensor when value should be updated
    req = 2  # requests a variable value
    internal = 3  # internal message
    stream = 4  # OTA firmware updates


class Presentation(BaseConst):
    """MySensors presentation sub-types."""

    S_DOOR = 0  # Door and window sensors
    S_MOTION = 1  # Motion sensors
    S_SMOKE = 2  # Smoke sensor
    S_LIGHT = 3  # Light Actuator (on/off)
    S_DIMMER = 4  # Dimmable device of some kind
    S_COVER = 5  # Window covers or shades
    S_TEMP = 6  # Temperature sensor
    S_HUM = 7  # Humidity sensor
    S_BARO = 8  # Barometer sensor (Pressure)
    S_WIND = 9  # Wind sensor
    S_RAIN = 10  # Rain sensor
    S_UV = 11  # UV sensor
    S_WEIGHT = 12  # Weight sensor for scales etc.
    S_POWER = 13  # Power measuring device, like power meters
    S_HEATER = 14  # Heater device
    S_DISTANCE = 15  # Distance sensor
    S_LIGHT_LEVEL = 16  # Light sensor
    S_ARDUINO_NODE = 17  # Arduino node device
    S_ARDUINO_RELAY = 18  # Arduino repeating node device
    S_LOCK = 19  # Lock device
    S_IR = 20  # Ir sender/receiver device
    S_WATER = 21  # Water meter
    S_AIR_QUALITY = 22  # Air quality sensor e.g. MQ-2
    S_CUSTOM = 23  # Use this for custom sensors
    S_DUST = 24  # Dust level sensor
    S_SCENE_CONTROLLER = 25  # Scene controller device


class SetReq(BaseConst):
    """MySensors set/req sub-types."""

    V_TEMP = 0  # Temperature
    V_HUM = 1  # Humidity
    V_LIGHT = 2  # Light status. 0=off 1=on
    V_DIMMER = 3  # Dimmer value. 0-100%
    V_PRESSURE = 4  # Atmospheric Pressure
    # Weather forecast. One of "stable", "sunny", "cloudy", "unstable",
    # "thunderstorm" or "unknown"
    V_FORECAST = 5
    V_RAIN = 6  # Amount of rain
    V_RAINRATE = 7  # Rate of rain
    V_WIND = 8  # Windspeed
    V_GUST = 9  # Gust
    V_DIRECTION = 10  # Wind direction
    V_UV = 11  # UV light level
    V_WEIGHT = 12  # Weight (for scales etc)
    V_DISTANCE = 13  # Distance
    V_IMPEDANCE = 14  # Impedance value
    # Armed status of a security sensor.  1=Armed, 0=Bypassed
    V_ARMED = 15
    # Tripped status of a security sensor. 1=Tripped, 0=Clear
    V_TRIPPED = 16
    V_WATT = 17  # Watt value for power meters
    V_KWH = 18  # Accumulated number of KWH for a power meter
    V_SCENE_ON = 19  # Turn on a scene
    V_SCENE_OFF = 20  # Turn off a scene
    # Mode of heater. One of "Off", "HeatOn", "CoolOn", or "AutoChangeOver"
    V_HEATER = 21
    V_HEATER_SW = 22  # Heater switch power. 1=On, 0=Off
    V_LIGHT_LEVEL = 23  # Light level. 0-100%
    V_VAR1 = 24  # Custom value
    V_VAR2 = 25  # Custom value
    V_VAR3 = 26  # Custom value
    V_VAR4 = 27  # Custom value
    V_VAR5 = 28  # Custom value
    V_UP = 29  # Window covering. Up.
    V_DOWN = 30  # Window covering. Down.
    V_STOP = 31  # Window covering. Stop.
    V_IR_SEND = 32  # Send out an IR-command
    V_IR_RECEIVE = 33  # This message contains a received IR-command
    V_FLOW = 34  # Flow of water (in meter)
    V_VOLUME = 35  # Water volume
    V_LOCK_STATUS = 36  # Set or get lock status. 1=Locked, 0=Unlocked
    V_DUST_LEVEL = 37  # Dust level
    V_VOLTAGE = 38  # Voltage level
    V_CURRENT = 39  # Current level


class Internal(BaseConst):
    """MySensors internal sub-types."""

    # Use this to report the battery level (in percent 0-100).
    I_BATTERY_LEVEL = 0
    # Sensors can request the current time from the Controller using this
    # message. The time will be reported as the seconds since 1970
    I_TIME = 1
    # Sensors report their library version at startup using this message type
    I_VERSION = 2
    # Use this to request a unique node id from the controller.
    I_ID_REQUEST = 3
    # Id response back to sensor. Payload contains sensor id.
    I_ID_RESPONSE = 4
    # Start/stop inclusion mode of the Controller (1=start, 0=stop).
    I_INCLUSION_MODE = 5
    # Config request from node. Reply with (M)etric or (I)mperal back to sensor
    I_CONFIG = 6
    # When a sensor starts up, it broadcast a search request to all neighbor
    # nodes. They reply with a I_FIND_PARENT_RESPONSE.
    I_FIND_PARENT = 7
    # Reply message type to I_FIND_PARENT request.
    I_FIND_PARENT_RESPONSE = 8
    # Sent by the gateway to the Controller to trace-log a message
    I_LOG_MESSAGE = 9
    # A message that can be used to transfer child sensors
    # (from EEPROM routing table) of a repeating node.
    I_CHILDREN = 10
    # Optional sketch name that can be used to identify sensor in the
    # Controller GUI
    I_SKETCH_NAME = 11
    # Optional sketch version that can be reported to keep track of the version
    # of sensor in the Controller GUI.
    I_SKETCH_VERSION = 12
    # Used by OTA firmware updates. Request for node to reboot.
    I_REBOOT = 13
    # Send by gateway to controller when startup is complete
    I_GATEWAY_READY = 14


class Stream(BaseConst):
    """MySensors stream sub-types."""

    # Request new FW, payload contains current FW details
    ST_FIRMWARE_CONFIG_REQUEST = 0
    # New FW details to initiate OTA FW update
    ST_FIRMWARE_CONFIG_RESPONSE = 1
    ST_FIRMWARE_REQUEST = 2  # Request FW block
    ST_FIRMWARE_RESPONSE = 3  # Response FW block
    ST_SOUND = 4  # Sound
    ST_IMAGE = 5  # Image


VALID_MESSAGE_TYPES = {
    MessageType.presentation: list(Presentation),
    MessageType.set: list(SetReq),
    MessageType.req: list(SetReq),
    MessageType.internal: list(Internal),
    MessageType.stream: list(Stream),
}

VALID_PRESENTATION = {member: str for member in list(Presentation)}
VALID_PRESENTATION.update(
    {Presentation.S_ARDUINO_NODE: is_version, Presentation.S_ARDUINO_RELAY: is_version}
)

VALID_TYPES = {
    Presentation.S_DOOR: [SetReq.V_TRIPPED, SetReq.V_ARMED],
    Presentation.S_MOTION: [SetReq.V_TRIPPED, SetReq.V_ARMED],
    Presentation.S_SMOKE: [SetReq.V_TRIPPED, SetReq.V_ARMED],
    Presentation.S_LIGHT: [SetReq.V_LIGHT, SetReq.V_WATT],
    Presentation.S_DIMMER: [SetReq.V_LIGHT, SetReq.V_DIMMER, SetReq.V_WATT],
    Presentation.S_COVER: [SetReq.V_UP, SetReq.V_DOWN, SetReq.V_STOP, SetReq.V_DIMMER],
    Presentation.S_TEMP: [SetReq.V_TEMP],
    Presentation.S_HUM: [SetReq.V_HUM],
    Presentation.S_BARO: [SetReq.V_PRESSURE, SetReq.V_FORECAST],
    Presentation.S_WIND: [SetReq.V_WIND, SetReq.V_GUST, SetReq.V_DIRECTION],
    Presentation.S_RAIN: [SetReq.V_RAIN, SetReq.V_RAINRATE],
    Presentation.S_UV: [SetReq.V_UV],
    Presentation.S_WEIGHT: [SetReq.V_WEIGHT, SetReq.V_IMPEDANCE],
    Presentation.S_POWER: [SetReq.V_WATT, SetReq.V_KWH],
    Presentation.S_HEATER: [SetReq.V_HEATER, SetReq.V_HEATER_SW, SetReq.V_TEMP],
    Presentation.S_DISTANCE: [SetReq.V_DISTANCE],
    Presentation.S_LIGHT_LEVEL: [SetReq.V_LIGHT_LEVEL],
    Presentation.S_ARDUINO_NODE: [],
    Presentation.S_ARDUINO_RELAY: [],
    Presentation.S_LOCK: [SetReq.V_LOCK_STATUS],
    Presentation.S_IR: [SetReq.V_IR_SEND, SetReq.V_IR_RECEIVE],
    Presentation.S_WATER: [SetReq.V_FLOW, SetReq.V_VOLUME],
    Presentation.S_AIR_QUALITY: [SetReq.V_DUST_LEVEL],
    Presentation.S_CUSTOM: [
        SetReq.V_VAR1,
        SetReq.V_VAR2,
        SetReq.V_VAR3,
        SetReq.V_VAR4,
        SetReq.V_VAR5,
    ],
    Presentation.S_DUST: [SetReq.V_DUST_LEVEL],
    Presentation.S_SCENE_CONTROLLER: [SetReq.V_SCENE_ON, SetReq.V_SCENE_OFF],
}

LOGICAL_ZERO = "0"
LOGICAL_ONE = "1"
OFF = "Off"
HEAT_ON = "HeatOn"
COOL_ON = "CoolOn"
AUTO_CHANGE_OVER = "AutoChangeOver"
STABLE = "stable"
SUNNY = "sunny"
CLOUDY = "cloudy"
UNSTABLE = "unstable"
THUNDERSTORM = "thunderstorm"
UNKNOWN = "unknown"
FORECASTS = (STABLE, SUNNY, CLOUDY, UNSTABLE, THUNDERSTORM, UNKNOWN)

VALID_SETREQ = {
    SetReq.V_TEMP: str,
    SetReq.V_HUM: str,
    SetReq.V_LIGHT: vol.In(
        [LOGICAL_ZERO, LOGICAL_ONE],
        msg=f"value must be either {LOGICAL_ZERO} or {LOGICAL_ONE}",
    ),
    SetReq.V_DIMMER: vol.All(
        percent_int,
        vol.Coerce(str),
        msg=f"value must be integer between {0} and {100}",
    ),
    SetReq.V_PRESSURE: str,
    SetReq.V_FORECAST: vol.Any(
        str,
        vol.In(
            FORECASTS,
            msg=f"forecast must be one of: {', '.join(FORECASTS)}",
        ),
    ),
    SetReq.V_RAIN: str,
    SetReq.V_RAINRATE: str,
    SetReq.V_WIND: str,
    SetReq.V_GUST: str,
    SetReq.V_DIRECTION: str,
    SetReq.V_UV: str,
    SetReq.V_WEIGHT: str,
    SetReq.V_DISTANCE: str,
    SetReq.V_IMPEDANCE: str,
    SetReq.V_ARMED: vol.In(
        [LOGICAL_ZERO, LOGICAL_ONE],
        msg=f"value must be either {LOGICAL_ZERO} or {LOGICAL_ONE}",
    ),
    SetReq.V_TRIPPED: vol.In(
        [LOGICAL_ZERO, LOGICAL_ONE],
        msg=f"value must be either {LOGICAL_ZERO} or {LOGICAL_ONE}",
    ),
    SetReq.V_WATT: str,
    SetReq.V_KWH: str,
    SetReq.V_SCENE_ON: str,
    SetReq.V_SCENE_OFF: str,
    SetReq.V_HEATER: vol.In(
        [OFF, HEAT_ON, COOL_ON, AUTO_CHANGE_OVER],
        msg=f"value must be one of: {OFF}, {HEAT_ON}, {COOL_ON} or {AUTO_CHANGE_OVER}",
    ),
    SetReq.V_HEATER_SW: vol.In(
        [LOGICAL_ZERO, LOGICAL_ONE],
        msg=f"value must be either {LOGICAL_ZERO} or {LOGICAL_ONE}",
    ),
    SetReq.V_LIGHT_LEVEL: vol.All(
        vol.Coerce(float),
        vol.Range(min=0.0, max=100.0),
        vol.Coerce(str),
        msg=f"value must be float between {0.0} and {100.0}",
    ),
    SetReq.V_VAR1: str,
    SetReq.V_VAR2: str,
    SetReq.V_VAR3: str,
    SetReq.V_VAR4: str,
    SetReq.V_VAR5: str,
    SetReq.V_UP: str,
    SetReq.V_DOWN: str,
    SetReq.V_STOP: str,
    SetReq.V_IR_SEND: str,
    SetReq.V_IR_RECEIVE: str,
    SetReq.V_FLOW: str,
    SetReq.V_VOLUME: str,
    SetReq.V_LOCK_STATUS: vol.In(
        [LOGICAL_ZERO, LOGICAL_ONE],
        msg=f"value must be either {LOGICAL_ZERO} or {LOGICAL_ONE}",
    ),
    SetReq.V_DUST_LEVEL: str,
    SetReq.V_VOLTAGE: str,
    SetReq.V_CURRENT: str,
}

CONF_METRIC = "M"
CONF_IMPERIAL = "I"
MAX_NODE_ID = 254

VALID_INTERNAL = {
    Internal.I_BATTERY_LEVEL: vol.All(
        percent_int,
        vol.Coerce(str),
        msg=f"value must be integer between {0} and {100}",
    ),
    Internal.I_TIME: vol.Any("", vol.All(vol.Coerce(int), vol.Coerce(str))),
    Internal.I_VERSION: str,
    Internal.I_ID_REQUEST: "",
    Internal.I_ID_RESPONSE: vol.All(
        vol.Coerce(int), vol.Range(min=1, max=MAX_NODE_ID), vol.Coerce(str)
    ),
    Internal.I_INCLUSION_MODE: vol.In([LOGICAL_ZERO, LOGICAL_ONE]),
    Internal.I_CONFIG: vol.Any(
        vol.All(vol.Coerce(int), vol.Range(min=0, max=MAX_NODE_ID)),
        CONF_METRIC,
        CONF_IMPERIAL,
    ),
    Internal.I_FIND_PARENT: "",
    Internal.I_FIND_PARENT_RESPONSE: vol.All(
        vol.Coerce(int), vol.Range(min=0, max=MAX_NODE_ID), vol.Coerce(str)
    ),
    Internal.I_LOG_MESSAGE: str,
    Internal.I_CHILDREN: str,
    Internal.I_SKETCH_NAME: str,
    Internal.I_SKETCH_VERSION: str,
    Internal.I_REBOOT: "",
    Internal.I_GATEWAY_READY: str,
}

VALID_STREAM = {
    Stream.ST_FIRMWARE_CONFIG_REQUEST: str,
    Stream.ST_FIRMWARE_CONFIG_RESPONSE: str,
    Stream.ST_FIRMWARE_REQUEST: str,
    Stream.ST_FIRMWARE_RESPONSE: str,
    Stream.ST_SOUND: str,
    Stream.ST_IMAGE: str,
}

VALID_PAYLOADS = {
    MessageType.presentation: VALID_PRESENTATION,
    MessageType.set: VALID_SETREQ,
    MessageType.req: {member: "" for member in list(SetReq)},
    MessageType.internal: VALID_INTERNAL,
    MessageType.stream: VALID_STREAM,
}
